#ifndef __messages_queue_HPP__
#define __messages_queue_HPP__

#include <sys/msg.h>

namespace ipc {

	class messages_queue {
		private:
			messages_queue ();
			messages_queue (const messages_queue& other);

		protected:
			key_t key;
			int id;

			/**
			 * Realiza un llamado especifico de control IPC sobre la cola.
			 */
			int ipc_control(int command, struct msqid_ds& stats) {
				int error = 0;
				if (*this) {
					if (msgctl(this->id, command, &stats) < 0)
						error = errno;;
				}
				return error;
			}

		public:
			/**
			 * Constructor de la cola de mensajes.
			 * @param key Clave generada a traves de ftok.
			 */
			messages_queue(const key_t& key) {
				this->key = key;
				this->id = -1;
			}

			/**
			 * Constructor de la cola de mensajes.
			 * @param key Modo de acceso al objeto IPC.
			 */
			int get(int mode) {
				int error = 0;
				if (this->id < 0) {
					int msgId = msgget(key,mode);
					if (msgId != -1)
						this->id = msgId;
					else
						error = errno;
				}
				return error;
			}


			/**
			 * Sobreescritura del operador ().
			 * @return true si la cola fue correctamente inicializada, false en caso contrario.
			 */
			operator bool() const {
				return (this->id > 0);
			}

			/**
			 * Completar la estructura pasada por parametro con el estado de la cola de mensajes.
			 * @param stats Estructura donde almacenar los datos de la cola de mensajes.
			 * @return Resultado de la invocacion a la obtencion de estado de la cola de mensajes.
			 */
			int get_stats (struct msqid_ds& stats) {
				return ipc_control(IPC_STAT,stats);
			}

			/**
			 * Establece permisos, operaciones y numero de bytes sobre la cola de mensajes a partir de la estructura establecida por parametro.
			 * @param stats Estructura con los datos a modificar.
			 * @return Resultado de la invocacion al cambio de permisos sobre la cola de mensajes.
			 */
			int set_permits(struct msqid_ds& stats) {
				return ipc_control(IPC_SET,stats);
			}

			/**
			 * @return Identificador de la cola de mensajes.
			 */
			int getId() const {
				return this->id;
			}

			/**
			 * @return Clave de cración de la cola de mensajes.
			 */
			key_t getKey() const {
				return this->key;
			}

			/**
			 * Destructor de la cola de mensajes, con liberacion de recursos.
			 */
			int destroy() {
				int error = 0;
				if (this->id > 0) {
					if (msgctl(this->id, IPC_RMID, 0) < 0);
						error = errno;
				}
				return error;
			}

			/**
			 * Envia un objeto a la cola.
			 * @param message Objeto a enviar.
			 * @param flags Flags del envio.
			 * @return Codigo de resultado.
			 */
			template<typename M>
			int send (M& message, int flags = 0) {
				int error = 0;
				if (*this) {
					size_t length = sizeof(M) - sizeof(long);
					if (msgsnd(this->id, &message, length, flags) < 0)
						error = errno;
				}
				return error;
			}

			/**
			 * Recibe un objeto de la cola.
			 * @param message Objeto a recibir.
			 * @param flags Flags de la recepcion.
			 * @return Codigo de resultado.
			 */
			template<typename M>
			int receive (M& message, int type = 0, int flags = 0) {
				int error = (getId() > 0) ? 0:-1;
				if (error == 0) {
					size_t length = sizeof(M) - sizeof(long);
					if (msgrcv(this->id, &message, length, type, flags) < 0)
						error = errno;
				}
				return error;
			}
	}; // messages_queue

class messages_queue_exception : public exception {
	public:
		messages_queue_exception (int msqId, key_t key, const std::string& message) : exception(msqId,key,message) {}
		
		virtual std::string getClass() const {
			return "Message queue";
		}
}; // messages_queue_exception

class messages_queue_with_exceptions {
	protected:
		messages_queue mq;

	public:
		messages_queue_with_exceptions(const key_t& key) : mq(key) {}

		inline int get(int mode) throw(messages_queue_exception) {
			if (mq.get(mode) != 0)
				throw messages_queue_exception(mq.getId(),mq.getKey(),"get");
			return 0;
		}

		inline int destroy() throw(messages_queue_exception) {
			if (mq.destroy() != 0)
				throw messages_queue_exception(mq.getId(),mq.getKey(),"destroy");
			return 0;
		}
		
		inline operator bool() const {
			return (bool)mq;
		}

		inline int getId() const {
			return mq.getId();
		}
		inline int getKey() const {
			return mq.getKey();
		}

		template<typename M>
		inline int send (M& message, int flags = 0) {
			if ( mq.send<M>(message,flags) != 0 )
				throw messages_queue_exception(mq.getId(),mq.getKey(),"send");
			return 0;
		}

		template<typename M>
		inline int receive (M& message, int type = 0, int flags = 0) {
			if ( mq.receive<M>(message,type,flags) != 0 )
				throw messages_queue_exception(mq.getId(),mq.getKey(),"receive");
			return 0;
		}
}; // messages_queue_with_exceptions

} // namespace ipc

#endif /* __COLA__HPP__ */
